using System;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;

#if NUNIT
using NUnit.Framework;
using TestClass = NUnit.Framework.TestFixtureAttribute;
// using ClassInitialize = NUnit.Framework.TestFixtureSetUpAttribute;
// using ClassCleanup = NUnit.Framework.TestFixtureTearDownAttribute;
using TestInitialize = NUnit.Framework.SetUpAttribute;
using TestCleanup = NUnit.Framework.TearDownAttribute;
using TestMethod = NUnit.Framework.TestAttribute;
#else
using Microsoft.VisualStudio.TestTools.UnitTesting;
#endif

using A4G.Core;
using A4G.Core.Algorithms;
using System.Text;

namespace A4G.Core.Tests
{
    [TestClass]
    public class HirschbergAlignmentTest
    {
        private const int minLengthOne = 1;
        private const int maxLengthOne = 32;

        private const int minLengthTwo = 1;
        private const int maxLengthTwo = 11;

        private static readonly char[] one = new char[] { 'A' };
        private static readonly char[] two = new char[] { 'A', 'C' };
        private static readonly char[] four = new char[] { 'A', 'C', 'G', 'T' };
        private readonly ScoringMatrix scoringMatrix = new ScoringMatrix(four);
        private const string resultDirName = @"Results";

        private readonly IGapMethodProvider constantGapMethodProvider = new PolinomialGapMethodProvider(-3.0);
        private readonly IGapMethodProvider polinomialGapMethodProvider = new PolinomialGapMethodProvider(new double[] { -7.26667, 6.035, -2.0681, 0.3517, -0.0291, 1.0 });
        private readonly IGapMethodProvider lookupGapMethodProvider = new LookupGapMethodProvider(new double[] { -3.0, -1.5, -1.25, -1.0 });

        public HirschbergAlignmentTest()
        {
            scoringMatrix.SetAsIdentityMatrix();
            scoringMatrix.SetNonDiagonalValues(-2.0);
            Directory.CreateDirectory(resultDirName);
        }

        [TestInitialize()]
        public void Initialize()
        {
        }

        [TestCleanup()]
        public void Cleanup()
        {
        }

        [TestMethod]
        public void TestSimple16Combinations()
        {
#if NUNIT
			string testfilePath = @"F:\SourceTree\BmvCoding\A4G\A4G.Core\A4G.Core.Tests\TestFiles\HirschbergAlignmentTestFiles\Simple16";
#else
            string testfilePath = @"HirschbergAlignmentTestFiles\Simple16";
#endif
            DirectoryInfo dirInfo = new DirectoryInfo(testfilePath);

            string outputFilename = string.Format("TestSimple16Combinations.hirschbergalignmentresult");
            outputFilename = Path.Combine(resultDirName, outputFilename);
            StreamWriter writer = new StreamWriter(outputFilename);

            string failedAlignments = string.Empty;
            foreach (FileInfo fileInfo1 in dirInfo.GetFiles("*.fsa"))
            {
                foreach (FileInfo fileInfo2 in dirInfo.GetFiles("*.fsa"))
                {
                    FastaFile inputFile1 = new FastaFile(fileInfo1.FullName);
                    inputFile1.Load();

                    FastaFile inputFile2 = new FastaFile(fileInfo2.FullName);
                    inputFile2.Load();

                    HirschbergAlignment alignment = new HirschbergAlignment(
                        ((Sequence)inputFile1.Sequences[0]).SequenceString,
                        ((Sequence)inputFile2.Sequences[0]).SequenceString,
                        scoringMatrix,
                        -3.0);

                    Trace.WriteLine(string.Format("Aligning {0} with {1}.", fileInfo1.Name, fileInfo2.Name), "HirschbergAlignmentTestTraceCategory");
                    AlignmentPath path = null;
                    try
                    {
                        path = alignment.GetAlignmentPath();
                        string[] results = path.GetAlignmentResults();

                        writer.WriteLine("");
                        foreach (string result in results)
                        {
                            writer.WriteLine(result);
                        }
                    }
                    catch (Exception e)
                    {
                        failedAlignments +=
                            string.Format("{0} with {1} failed: {2}\r\n", fileInfo1.Name, fileInfo2.Name, e.Message);
                    }
                    finally
                    {
                        path = null;
                        alignment = null;
                        inputFile1 = null;
                        inputFile2 = null;
                        GC.Collect();
                    }
                }
            }

            writer.WriteLine("");
            writer.WriteLine(failedAlignments);
            writer.Close();

            if (failedAlignments.Length > 0)
            {
                Assert.Fail(failedAlignments);
            }
        }

#if !NUNIT
        [Timeout(84239000)]
#endif
        [TestMethod]
        public void TestIBIVUCombinationsUsingConstantGapMethod()
        {
            TestIBIVUCombinations("TestIBIVUCombinationsUsingConstantGapMethod", scoringMatrix, constantGapMethodProvider);
        }

        //#if !NUNIT
        //        [Timeout(84239000)]
        //#endif
        //        [TestMethod]
        //        public void TestIBIVUCombinationsUsingPolinomialGapMethod()
        //        {
        //            TestIBIVUCombinations("TestIBIVUCombinationsUsingPolinomialGapMethod", scoringMatrix, polinomialGapMethodProvider);
        //        }

        //#if !NUNIT
        //        [Timeout(84239000)]
        //#endif
        //        [TestMethod]
        //        public void TestIBIVUCombinationsUsingLookupGapMethod()
        //        {
        //            TestIBIVUCombinations("TestIBIVUCombinationsUsingLookupGapMethod", scoringMatrix, lookupGapMethodProvider);
        //        }

        private void TestIBIVUCombinations(string name, ScoringMatrix matrix, IGapMethodProvider gapMethodProvider)
        {
#if NUNIT
			string testfilePath = @"F:\SourceTree\BmvCoding\A4G\A4G.Core\A4G.Core.Tests\TestFiles\HirschbergAlignmentTestFiles\IBIVU";
#else
            string testfilePath = @"HirschbergAlignmentTestFiles\IBIVU";
#endif
            DirectoryInfo dirInfo = new DirectoryInfo(testfilePath);

            string outputFilename = string.Format("{0}.hirschbergalignmentresult", name);
            outputFilename = Path.Combine(resultDirName, outputFilename);
            StreamWriter writer = new StreamWriter(outputFilename);

            string failedAlignments = string.Empty;
            foreach (FileInfo fileInfo1 in dirInfo.GetFiles("*.0"))
            {
                FastaFile inputFile1 = new FastaFile(fileInfo1.FullName);
                inputFile1.Load();

                string filename2 = Path.ChangeExtension(fileInfo1.FullName, "00");
                FastaFile inputFile2 = new FastaFile(filename2);
                inputFile2.Load();

                HirschbergAlignment alignment = new HirschbergAlignment(
                    ((Sequence)inputFile1.Sequences[0]).SequenceString,
                    ((Sequence)inputFile2.Sequences[0]).SequenceString,
                    matrix,
                    gapMethodProvider);

                Trace.WriteLine(string.Format("Aligning {0} with {1}.", fileInfo1.Name, filename2), "HirschbergAlignmentTestTraceCategory");
                AlignmentPath path = null;
                try
                {
                    path = alignment.GetAlignmentPath();
                    string[] results = path.GetAlignmentResults();

                    writer.WriteLine("");
                    foreach (string result in results)
                    {
                        writer.WriteLine(result);
                    }
                }
                catch (Exception e)
                {
                    failedAlignments +=
                        string.Format("{0} with {1} failed: {2}\r\n", fileInfo1.Name, filename2, e.Message);
                }
                finally
                {
                    path = null;
                    alignment = null;
                    inputFile1 = null;
                    inputFile2 = null;
                    GC.Collect();
                }
            }

            writer.WriteLine("");
            writer.WriteLine(failedAlignments);
            writer.Close();

            if (failedAlignments.Length > 0)
            {
                Assert.Fail(failedAlignments);
            }
        }

#if !NUNIT
        [Timeout(84239000)]
#endif
        [TestMethod]
        public void TestOneCharAllCombinations()
        {
            TestAllCombinations(one, minLengthOne, maxLengthOne);
        }

#if !NUNIT
        [Timeout(84239000)]
#endif
        [TestMethod]
        public void TestTwoCharAllCombinations()
        {
            TestAllCombinations(two, minLengthTwo, maxLengthTwo);
        }

        private void TestAllCombinations(char[] chars, int minLength, int maxLength)
        {
            List<string> strings = new List<string>();
            List<string> length1Strings = new List<string>();
            foreach (char c in chars)
            {
                length1Strings.Add(c.ToString());
            }
            BuildStringsRecursive(chars, strings, length1Strings, minLength, maxLength);

            string outputFilename = string.Format("Test{0}CharAllCombinations.hirschbergalignmentresult", chars.Length);
            outputFilename = Path.Combine(resultDirName, outputFilename);

            StreamWriter writer = new StreamWriter(outputFilename);

            string failedAlignments = string.Empty;
            foreach (string s1 in strings)
            {
                foreach (string s2 in strings)
                {
                    HirschbergAlignment alignment = new HirschbergAlignment(
                        s1,
                        s2,
                        scoringMatrix,
                        -3.0);

                    Trace.WriteLine(string.Format("Aligning {0} with {1}.", s1, s2), "HirschbergAlignmentTestTraceCategory");
                    AlignmentPath path = null;
                    try
                    {
                        path = alignment.GetAlignmentPath();
                        string[] results = path.GetAlignmentResults();

                        // writer.WriteLine("");
                        // foreach (string result in results)
                        // {
                        // 	writer.WriteLine(result);
                        // }
                    }
                    catch (Exception e)
                    {
                        string fail = string.Format("{0} with {1} failed: {2}", s1, s2, e.Message);
                        failedAlignments += fail;
                        writer.WriteLine(fail);
                        failedAlignments += Environment.NewLine;
                    }
                    finally
                    {
                        path = null;
                        alignment = null;
                        GC.Collect();
                    }
                }
            }

            // writer.WriteLine("");
            // writer.WriteLine(failedAlignments);
            writer.Close();

            if (failedAlignments.Length > 0)
            {
                Assert.Fail(failedAlignments);
            }
        }

        //[TestMethod]
        //public void TestMultiThreading()
        //{
        //    int size = 500;

        //    Random rnd = new Random(DateTime.Now.Millisecond);
        //    List<char> sequence = new List<char>(size);
        //    for (int i = 0; i < size; i++)
        //    {
        //        sequence.Add(four[rnd.Next(four.Length)]);
        //    }

        //    StringBuilder builder = new StringBuilder(size);
        //    builder.Append(sequence.ToArray());
        //    string seq = builder.ToString();

        //    StreamWriter writer = new StreamWriter(@"Results\TestMultiThreadingPerformanceMeasurements.csv");

        //    int maxThreads = 16;
        //    for (int i = 0; i < maxThreads; i++)
        //    {
        //        HirschbergAlignment alignment = new HirschbergAlignment(
        //            seq,
        //            seq,
        //            scoringMatrix,
        //            -3.0,
        //            i);

        //        long ticks1 = DateTime.Now.Ticks;

        //        AlignmentPath path = alignment.GetAlignmentPath();
        //        string[] results = path.GetAlignmentResults();
        //        long ticks2 = DateTime.Now.Ticks;

        //        writer.WriteLine(string.Format("{0},{1}", i, ticks2 - ticks1));

        //        Assert.AreEqual(seq, results[1],
        //            string.Format("First alignment result incorrect at {0} threads", i));
        //        Assert.AreEqual(seq, results[2],
        //            string.Format("Second alignment result incorrect at {0} threads", i));

        //        path = null;
        //        alignment = null;
        //        results = null;
        //        GC.Collect();
        //    }

        //    writer.Close();
        //}

        [TestMethod]
        public void TestShortWithLong1()
        {
            string sequence1 = "AAAAACCCCC";
            string sequence2 = "AAAAATTTTTGGGGGCCCCC";
            Test(sequence1, sequence2);
        }

        [TestMethod]
        public void TestShortWithLong2()
        {
            string sequence1 = "AAAAACCCCC";
            string sequence2 = "AAAAATTTTTGGGGGCCCCCAAAAATTTTTGGGGGCCCCC";
            Test(sequence1, sequence2);
        }

        [TestMethod]
        public void TestAWithA()
        {
            Test("A", "A");
        }

        [TestMethod]
        public void TestAWithAA()
        {
            Test("A", "AA");
        }

        [TestMethod]
        public void TestAAAAAAAAWithAAAA()
        {
            Test("AAAAAAAA", "AAAA");
        }

        [TestMethod]
        public void TestACACWithCACA()
        {
            string sequence1 = "ACACA";
            string sequence2 = "CACAC";
            Test(sequence1, sequence2);
        }

        [TestMethod]
        public void TestCAAACWithACCA()
        {
            string sequence1 = "CAAAC";
            string sequence2 = "ACCA";
            Test(sequence1, sequence2);
        }

        [TestMethod]
        public void TestIBIVU303()
        {
            string sequence1 = "ACGCTGA";
            string sequence2 = "CTGAACG";
            Test(sequence1, sequence2, constantGapMethodProvider);
            // Test(sequence1, sequence2, polinomialGapMethodProvider);
            // Test(sequence1, sequence2, lookupGapMethodProvider);
        }

        [TestMethod]
        public void TestIBIVU502()
        {
            string sequence1 = "CTCAAAGTGAGAGACTTAGG";
            string sequence2 = "TTTGTGAAAAAAGAGTTCTA";
            Test(sequence1, sequence2, constantGapMethodProvider);
            // Test(sequence1, sequence2, polinomialGapMethodProvider);
            // Test(sequence1, sequence2, lookupGapMethodProvider);
        }

        [TestMethod]
        public void TestRandom128()
        {
            TestRandom(128);
        }
        [TestMethod]
        public void TestRandom256()
        {
            TestRandom(256);
        }
        [TestMethod]
        public void TestRandom512()
        {
            TestRandom(512);
        }
        [TestMethod]
        public void TestRandom1024()
        {
            TestRandom(1024);
        }
        [TestMethod]
        public void TestRandom2048()
        {
            TestRandom(2048);
        }
        [TestMethod]
        public void TestRandom4096()
        {
            TestRandom(4096);
        }

        private void TestRandom(int size)
        {
            Random r = new Random();
            string sequence1 = GetRandomSequence(r, size);
            string sequence2 = GetRandomSequence(r, size);
            Test(sequence1, sequence2, constantGapMethodProvider);
        }

        private string GetRandomSequence(Random r, int size)
        {
            char[] chars = new char[] { 'A', 'C', 'G', 'T' };
            StringBuilder sb = new StringBuilder(size);
            for (int i = 0; i < size; i++)
            {
                int charIndex = r.Next(0, 3);
                sb.Append(chars[charIndex]);
            }
            return sb.ToString();
        }

        private void Test(string sequence1, string sequence2)
        {
            Test(sequence1, sequence2, constantGapMethodProvider);
        }

        private void Test(string sequence1, string sequence2, IGapMethodProvider provider)
        {
            HirschbergAlignment alignment1 = new HirschbergAlignment(
                sequence1,
                sequence2,
                scoringMatrix,
                provider);
            AlignmentPath path1 = alignment1.GetAlignmentPath();
            string[] results1 = path1.GetAlignmentResults();

            HirschbergAlignment alignment2 = new HirschbergAlignment(
                sequence2,
                sequence1,
                scoringMatrix,
                -3.0);
            AlignmentPath path2 = alignment2.GetAlignmentPath();
            string[] results2 = path2.GetAlignmentResults();
        }

        private void BuildStringsRecursive(char[] chars, List<string> strings, List<string> lengthStrings, int minLength, int maxLength)
        {
            if ((lengthStrings[0].Length >= minLength) && (lengthStrings[0].Length <= maxLength))
            {
                strings.AddRange(lengthStrings);
            }

            List<string> newLengthStrings = new List<string>();
            foreach (string s in lengthStrings)
            {
                foreach (char c in chars)
                {
                    newLengthStrings.Add(s + c);
                }
            }

            if (newLengthStrings[0].Length <= maxLength)
            {
                BuildStringsRecursive(chars, strings, newLengthStrings, minLength, maxLength);
            }
        }
    }
}
